<?php
// +----------------------------------------------------------------------
// | 微信公众平台 OffiAccount
// | 1、填写服务器配置
// | 2、验证服务器地址的有效性
// | 3、依据接口文档实现业务逻辑
// +----------------------------------------------------------------------
// | 微信小程序  MiniProgram
// +----------------------------------------------------------------------
// | 微信开发平台 Oplatform
// | 1、移动应用：分享与收藏，微信支付，微信登陆，微信智能接口
// | 2、网站应用：微信登陆，微信智能接口
// | 3、小程序硬件框架
// +----------------------------------------------------------------------

namespace helper\tencent;

use helper\Facade;
use helper\Client;
use think\facade\Cache;

class Wechat
{
    /**
     * @var array|string[]
     */
    protected array $config = [
        'base_uri' => '',
        'appId' => '',
        'appSecret' => '',
        'token' => '',
        'encodingAESKey' => ''
    ];

    /**
     * @var string
     */
    protected string $access_token;

    /**
     * @var Client
     */
    protected Client $client;

    /**
     * @var array|string[]
     */
    private array $tpl = [
        'content' => ''
    ];

    /**
     * 初始化
     * @param array $config
     */
    public function __construct(array $config)
    {
        $this->config = $config;
        $this->client = new Client(['base_uri' => 'https://api.weixin.qq.com/', 'timeout' => 30]);
    }

    public function setConfig(array $config)
    {
        $this->config = $config;
    }

    public function getConfig(): array
    {
        return $this->config;
    }

    /**
     * 小程序 / 公众号
     * 接口调用凭证
     * @param string $path
     * @return string
     */
    public function setAccessToken(string $path)
    {
        $filename = $path . $this->config['appId'] . '.json'; //用于保存access token
        if (!file_exists($filename)) {
            file_put_contents($filename, '');
        }
        $data = json_decode(file_get_contents($filename));
        if (empty($data) || $data->expire_time < time()) {
            $url = "cgi-bin/token?grant_type=client_credential&appid={$this->config['appId']}&secret={$this->config['appSecret']}";
            $response = $this->client->get($url);
            $data = json_decode($response->getContents());
            $access_token = $data->access_token;
            $this->access_token = $access_token;
            if ($access_token) {
                $data->expire_time = time() + $data->expires_in;
                $data->access_token = $access_token;
                file_put_contents($filename, json_encode($data));
            }
        } else {
            $access_token = $data->access_token;
        }
        return $access_token;
    }

    public function getAccessToken(): string
    {
        return $this->access_token;
    }

    /**
     * 开放平台 / 微信网页开发 / 小程序
     * 微信网页开发 微信授权登录流程
     * 第一步：用户同意授权，获取code
     * 第二步：通过 code 获取 access_token
     * 第三步：刷新access_token（如果需要）
     * 第四步：拉取用户信息(需scope为 snsapi_userinfo)
     * 小程序登录 微信授权登录流程
     * 第一步：wx.login获取 用户临时登录凭证code
     * 第二步：wx.getUserInfo获取加密过的数据encryptedData和解密参数iv
     * 第三步：把步骤一、二中的code、encryptedData、iv传到开发者自己服务端
     * 第四步：服务端获取到code、encryptedData、iv之后用get方法请求微信接口
     * 调用 wx.checkSession 接口检测当前用户登录态是否有效，过期后开可以再调用wx.login获取新的用户登录态。调用成功说明当前未过期，调用失败说明已过期
     *
     * 开放平台 网站应用 / 微信网页开发
     * 获取链接地址
     * @param string $redirect_uri
     * @param string $scope snsapi_base(无需授权),snsapi_userinfo（需要授权）,snsapi_login(网站应用)
     * @param string $state
     * @return string
     */
    public function getCode($redirect_uri, $scope = 'snsapi_userinfo', $state = 'wechat')
    {
        $url = $scope == "snsapi_base" ? 'https://open.weixin.qq.com/connect/oauth2/authorize' : 'https://open.weixin.qq.com/connect/qrconnect';
        return $url . "?appid={$this->config['appId']}&redirect_uri={$redirect_uri}&response_type=code&scope={$scope}&state={$state}#wechat_redirect";
    }

    /**
     * 开放平台 / 微信网页开发 / 小程序
     * 通过code换取网页授权access_token
     * @param string $code
     * @param int $type 0,微信，1,小程序
     * @return array
     */
    public function getToken(string $code, int $type = 0)
    {
        // 小程序调用 auth.code2Session 接口，换取 用户唯一标识 OpenID 和 会话密钥 session_key。
        $url = $type
            ? "sns/jscode2session?appid={$this->config['appId']}&secret={$this->config['appSecret']}&js_code=$code&grant_type=authorization_code"
            : "sns/oauth2/access_token?appid={$this->config['appId']}&secret={$this->config['appSecret']}&code=$code&grant_type=authorization_code";

        $response = $this->client->get($url);
        if ($response->getStatusCode() != 200) {
            return [];
        }
        return $response->getBody();
        //{
        //    "access_token": "ACCESS_TOKEN",
        //    "expires_in": 7200,
        //    "refresh_token": "REFRESH_TOKEN",
        //    "openid": "OPENID",
        //    "scope": "SCOPE"
        //}

        // 小程序
        // ['openid','session_key','unionid','errcode','errmsg']
        // session_key    应用: 1)用户信息 2)手机号信息; 用户登录态拥有一定的时效性,退出使用会失效
    }

    /**
     * app登录 / 微信网页开发 / 小程序
     * 获取用户个人信息（UnionID 机制）
     * @param $access_token
     * @param $openid
     * @return array
     */
    public function getUserinfo($access_token, $openid)
    {
        $url = "sns/userinfo?access_token=$access_token&openid=$openid";
        $response = $this->client->get($url);
        if ($response->getStatusCode() != 200) {
            return [];
        }

        $data = $response->getBody();
        //$data['expire_time'] = date('Y-m-d H:i:s', time() + $data['expires_in']);
        return $data;
        // {
        //  "openid": "OPENID",
        //  "nickname": "NICKNAME",
        //  "sex": 1,
        //  "province": "PROVINCE",
        //  "city": "CITY",
        //  "country": "COUNTRY",
        //  "headimgurl": "https://thirdwx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/0",
        //  "privilege": ["PRIVILEGE1", "PRIVILEGE2"],
        //  "unionid": " o6_bmasdasdsad6_2sgVt7hMZOPfL"
        //}
    }

    /**
     * app登录 / 微信网页开发
     * 网页授权 刷新access_token
     * 刷新或续期 access_token
     */
    public function refresh_token($refresh_token)
    {
        $url = "sns/oauth2/refresh_token?appId={$this->config['appId']}&grant_type=refresh_token&refresh_token=$refresh_token";
        $response = $this->client->get($url);
        return $response->getBody();
    }

    /**
     * app登录 / 微信网页开发
     * 网页授权 检验授权凭证（access_token）是否有效
     */
    public function check_access_token($access_token)
    {
        $url = "sns/auth?access_token=$access_token&openid={$this->config['appId']}";
        $response = $this->client->get($url);
        return $response->getBody();
    }

    /**
     * 用户手机号
     * @return array
     */
    public function getPhoneNumber($code)
    {
        $url = "wxa/business/getuserphonenumber?access_token=$this->access_token";
        $response = $this->client->post($url, ['body' => ['code' => $code]]);
        return $response->getBody();
    }

    /**
     * 自定义菜单
     * 1，创建菜单
     **/
    public function createMenu($menu)
    {
        $url = "cgi-bin/menu/create?access_token=" . $this->access_token;
        $response = $this->client->post($url, ['body' => $menu]);
        return $response->getBody();
    }

    /**
     * 自定义菜单
     * 2，查询菜单
     **/
    public function queryMenu()
    {
        $url = "cgi-bin/get_current_selfmenu_info?access_token=" . $this->access_token;
        $response = $this->client->get($url);
        return $response->getBody();
    }

    /**
     * 自定义菜单
     * 3，删除菜单
     **/
    public function deleteMenu()
    {
        $url = "cgi-bin/menu/delete?access_token=" . $this->access_token;
        $response = $this->client->get($url);
        return $response->getBody();
    }

    /**
     * 自定义菜单
     * 4，事件推送
     * @postObj:响应的消息对象
     **/
    private function doEvent($postObj)
    {
        switch ($postObj->Event) {
            case  'subscribe': //订阅
                $this->doSubscribe($postObj);
                break;
            case 'unsubscribe': //取消订阅
                $this->doUnsubscribe($postObj);
                break;
            case 'CLICK'://点击菜单拉取消息时的事件推送
                $this->doClick($postObj);
                break;
            case 'TEMPLATESENDJOBFINISH'://模版消息发送结果通知
                $this->doMessage($postObj);
                break;
            case 'user_info_modified'://用户资料变更
                $this->doMessage($postObj);
                break;
            default:
                ;
        }
    }

    /**
     * 处理关注事件
     * @postObj:响应的消息对象
     **/
    private function doSubscribe($postObj)
    {
        $str = sprintf($this->tpl(), $postObj->FromUserName, $postObj->ToUserName, time(), '欢迎您关注忆昔网！');
        echo $str;
    }

    /**
     * 处理取消关注事件
     * @postObj:响应的消息对象
     **/
    private function doUnsubscribe($postObj)
    {
        ;//把用户的信息从数据库中删除
    }

    /**
     * 处理菜单点击事件
     * @postObj:响应的消息对象
     **/
    private function doClick($postObj)
    {

    }

    /**
     * 基础消息能力
     * 被动回复用户消息
     **/
    public function responseMsg()
    {
        //get post data, May be due to the different environments
        $postStr = file_get_contents('php://input');//$GLOBALS["HTTP_RAW_POST_DATA"];//获得用户发送信息
        if (empty($postStr)) {
            echo "";
            exit;
        }

        $postObj = simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);//解析XML到对象
        //接收普通消息
        switch ($postObj->MsgType) {
            case 'event': //事件处理
                $this->doEvent($postObj);
                break;
            case 'text': //文本消息
                $this->doText($postObj);
                break;
            case 'image': //图片消息
                $this->doImage($postObj);
                break;
            case 'voice': //语音消息
                $this->doVoice($postObj);
                break;
            case 'video': //视频处理
                $this->doVideo($postObj);
                break;
            case 'shortvideo':// 小视频消息
                break;
            case 'location'://地理位置消息
                $this->doLocation($postObj);
                break;
            case 'link'://链接消息
                $this->doLink($postObj);
                break;
            default:
                exit;
        }
    }

    /**
     * 设置文本消息
     * @param $text
     * @return mixed
     */
    public function setText($text)
    {
        return $this->tpl['text'] = $text;
    }

    /**
     * 回复文本消息
     * @postObj:响应的消息对象
     **/
    private function doText($postObj)
    {
        $fromUsername = $postObj->FromUserName;
        $toUsername = $postObj->ToUserName;
        $toUser = strval($toUsername);
        $keyword = trim($postObj->Content);
        $cache = Cache::store('redis');
        if (in_array($keyword, [1, 2, 3])) {
            $name = 'wechat_ocr_option:' . $toUser;
            $cache->set($name, $keyword);
            $menu = [
                1 => '通用文字识别',
                2 => '身份证识别',
                3 => '营业执照识别'
            ];
            $title = $menu[$keyword];
            $desc  = "请发送图片" . PHP_EOL;
            //$desc .= '使用情况：' . $cache->get('wechat_ocr_used:' . $keyword) . '/500';
            $img = 'https://bengbu.link/static/style/res/aliyun_ocr.png';
            $url = 'https://bengbu.link/';
            $resultStr = sprintf($this->tpl('news'), $fromUsername, $toUsername, time(), $title, $desc, $img, $url);
        } else {
            $contentStr  = '您好，很高兴为您服务，您可在对话框内输入以下数字进行解答' . PHP_EOL;
            $contentStr .= '【1】通用文字识别' . PHP_EOL;
            $contentStr .= '【2】身份证识别' . PHP_EOL;
            $contentStr .= '【3】营业执照识别';
            $resultStr = sprintf($this->tpl(), $fromUsername, $toUsername, time(), $contentStr);
        }
        echo $resultStr;die;
    }

    /**
     * 回复图片消息
     * @postObj:响应的消息对象
     **/
    private function doImage($postObj)
    {
        $fromUsername = $postObj->FromUserName;
        $toUsername = $postObj->ToUserName;
        $toUser = strval($toUsername);
        $cache = Cache::store('redis');
        $name = 'wechat_ocr_option:' . $toUser;
        $option = $cache->get($name, 0);
        $content = $postObj->PicUrl;
        if ($option) {
            $content = '识别失败';
            switch ($option) {
                case 1:
                    $ocr = Facade::Ocr( 'baidu', $this->config['baidu']);
                    $ocr->setAccessToken(runtime_path());
                    $res = $ocr->general($postObj->PicUrl);
                    if (isset($res['words_result_num'])) {
                        $word = array_column($res['words_result'],  'words');
                        $content = implode("\n", $word);
                    }
                    break;
                case 2:
                    $res = Facade::Ocr( 'ali', $this->config['ali'])->idCard($postObj->PicUrl);
                    if (isset($res['success'])) {
                        $content = '姓名  ' . $res['name'] . "\n";
                        $content .= '性别  ' . $res['sex'] . "    民族  " . $res['nationality'] . "\n";
                        $content .= '出生  ' . $res['birth'] . "\n";
                        $content .= '住址  ' . $res['address'] . "\n";
                        $content .= '公民身份号码  ' . $res['num'];
                    }
                    break;
                case 3:
                    $res = Facade::Ocr( 'ali', $this->config['ali'])->businessLicense($postObj->PicUrl);
                    if (isset($res['success'])) {
                        $content = '名称     ' . $res['name'] . "\n";
                        $content .= '经营者  ' . $res['person'] . "\n";
                        $content .= '注册号  ' . $res['reg_num'] . "\n";
                        $content .= '地址     ' . $res['address'] . "\n";
                        if ($res['name'] != 'FailInRecognition') {
                            //$content .= '企查查 https://www.qcc.com/web/search?key=' . urlencode($res['name']) . "\n";
                            $content .= '爱企查 https://aiqicha.baidu.com/s?q=' . urlencode($res['name']);
                        }
                    }
                    break;
            }
            // 个人使用情况
            //$name = 'wechat_ocr_user:' . $toUser;
            //$cache->inc($name);

            // 使用总量
            //$name  = "config:ocr";
            //$cache->inc('wechat_ocr_used:' . $option);
        }

        $str = sprintf($this->tpl(), $fromUsername, $toUsername, time(), $content);
        echo $str;
    }

    /**
     * 语音消息
     * @return void
     */
    private function doVoice($postObj)
    {
        $str = sprintf($this->tpl(), $postObj->FromUserName, $postObj->ToUserName, time(), '您发送的语音在' . $postObj->PicUrl . "。");
        echo $str;
    }

    /**
     * 视频消息
     * @param $postObj
     * @return void
     */
    private function doVideo($postObj)
    {
        $str = sprintf($this->tpl(), $postObj->FromUserName, $postObj->ToUserName, time(), '您发送的视频在' . $postObj->PicUrl . "。");
        echo $str;
    }

    /**
     * 小视频消息
     * @param $postObj
     * @return void
     */
    private function doShortVideo($postObj)
    {
        $str = sprintf($this->tpl(), $postObj->FromUserName, $postObj->ToUserName, time(), '您发送的小视频在' . $postObj->PicUrl . "。");
        echo $str;
    }

    /**
     * 回复音乐消息
     * @postObj:响应的消息对象
     **/
    private function doMusic($postObj)
    {
        $content = $postObj->Content;
        $content = mb_substr($content, 2, mb_strlen($content, 'utf-8') - 2, 'utf-8');//删除字符串前两个字符（删除”歌曲“）
        $arr = explode('@', $content);//分解歌曲和歌手到数组
        $song = $arr[0];
        $singer = '';
        if (isset($arr[1])) {//生成有歌曲和歌手的音乐搜索地址
            $singer = $arr[1];
            $curl = 'http://box.zhangmen.baidu.com/x?op=12&count=1&title=' . $arr[0] . '$$' . $arr[1] . '$$$$';
        } else //搜索仅有歌曲的地址
            $curl = 'http://box.zhangmen.baidu.com/x?op=12&count=1&title=' . $arr[0] . '$$';
        $response = $this->_request($curl, false);//开始搜索
        $res = simplexml_load_string($response, 'SimpleXMLElement', LIBXML_NOCDATA);//搜索结果解析
        $encode = $res->url->encode;
        $decode = $res->url->decode;
        $musicurl = mb_substr($encode, 0, mb_strrpos($encode, '/', 'utf-8') + 1, 'utf-8') .
            mb_substr($decode, 0, mb_strrpos($decode, '&', 'utf-8'), 'utf-8');
        file_put_contents('./tmp', mb_substr($encode, 0, mb_strrchr($encode, '/', 'utf-8') + 1, 'utf-8'));//生成歌曲的实际地址
        $str = sprintf($this->tpl['music'], $postObj->FromUserName, $postObj->ToUserName, time(), $arr[0], $arr[1], $musicurl, $musicurl, "FZIwAG_Vzbj0zEelbUScRJmExgKJG0x6D9krMv0wiTYwC3PLR_HiGPD58gHY4P3q");//发送歌曲到用户
        echo $str;
        exit;
    }

    /**
     * 地理位置消息
     * @postObj:响应的消息对象
     **/
    private function doLocation($postObj)
    {
        $str = sprintf($this->tpl(), $postObj->FromUserName, $postObj->ToUserName, time(), "您所在的位置是经度：" . $postObj->Location_Y . "，纬度：" . $postObj->Location_X . "。");
        echo $str;
    }

    /**
     * 回复图文消息
     * @return void
     */
    public function doNews()
    {

    }

    /**
     * 链接消息
     * @return void
     */
    private function doLink()
    {
        ;
    }

    /**
     * 消息模板
     * @param string $type
     * @return string
     */
    private function tpl(string $type = 'text'): string
    {
        $tpl = [
            'text' => "<xml>
            <ToUserName><![CDATA[%s]]></ToUserName>
            <FromUserName><![CDATA[%s]]></FromUserName>
            <CreateTime>%s</CreateTime>
            <MsgType><![CDATA[text]]></MsgType>
            <Content><![CDATA[%s]]></Content>
        </xml>",
            'image' => '<xml>
          <ToUserName><![CDATA[%s]]></ToUserName>
          <FromUserName><![CDATA[%s]]></FromUserName>
          <CreateTime>%s</CreateTime>
          <MsgType><![CDATA[image]]></MsgType>
          <Image>
            <MediaId><![CDATA[%s]]></MediaId>
          </Image>
        </xml>',
            'link' => '<xml>
          <ToUserName><![CDATA[%s]]></ToUserName>
          <FromUserName><![CDATA[%s]]></FromUserName>
          <CreateTime>%s</CreateTime>
          <MsgType><![CDATA[link]]></MsgType>
          <Title><![CDATA[%s]]></Title>
          <Description><![CDATA[%s]]></Description>
          <Url><![CDATA[%s]]></Url>
          <MsgId><![CDATA[%s]]></MsgId>
        </xml>',
            'voice' => '<xml>
          <ToUserName><![CDATA[%s]]></ToUserName>
          <FromUserName><![CDATA[%s]]></FromUserName>
          <CreateTime>%s</CreateTime>
          <MsgType><![CDATA[voice]]></MsgType>
          <Voice>
            <MediaId><![CDATA[%s]]></MediaId>
          </Voice>
        </xml>',
            'video' => '<xml>
          <ToUserName><![CDATA[toUser]]></ToUserName>
          <FromUserName><![CDATA[fromUser]]></FromUserName>
          <CreateTime>12345678</CreateTime>
          <MsgType><![CDATA[video]]></MsgType>
          <Video>
            <MediaId><![CDATA[media_id]]></MediaId>
            <Title><![CDATA[title]]></Title>
            <Description><![CDATA[description]]></Description>
          </Video>
        </xml>',
            'shortvideo' => '<xml>
          <ToUserName><![CDATA[%s]]></ToUserName>
          <FromUserName><![CDATA[%s]]></FromUserName>
          <CreateTime>%s</CreateTime>
          <MsgType><![CDATA[shortvideo]]></MsgType>
          <MediaId><![CDATA[media_id]]></MediaId>
          <ThumbMediaId><![CDATA[thumb_media_id]]></ThumbMediaId>
          <MsgId>1234567890123456</MsgId>
        </xml>',
            'news' => '<xml>
          <ToUserName><![CDATA[%s]]></ToUserName>
          <FromUserName><![CDATA[%s]]></FromUserName>
          <CreateTime>%s</CreateTime>
          <MsgType><![CDATA[news]]></MsgType>
          <ArticleCount>1</ArticleCount>
          <Articles>
            <item>
              <Title><![CDATA[%s]]></Title>
              <Description><![CDATA[%s]]></Description>
              <PicUrl><![CDATA[%s]]></PicUrl>
              <Url><![CDATA[%s]]></Url>
            </item>
          </Articles>
        </xml>',
            'location' => '<xml>
          <ToUserName><![CDATA[toUser]]></ToUserName>
          <FromUserName><![CDATA[fromUser]]></FromUserName>
          <CreateTime>1351776360</CreateTime>
          <MsgType><![CDATA[location]]></MsgType>
          <Location_X>23.134521</Location_X>
          <Location_Y>113.358803</Location_Y>
          <Scale>20</Scale>
          <Label><![CDATA[位置信息]]></Label>
          <MsgId>1234567890123456</MsgId>
        </xml>'
        ];
        return $tpl[$type];
    }

    /**
     * 模板消息
     * 发送模板消息
     */
    public function sendMessage($post)
    {
        $url = "cgi-bin/message/template/send?access_token=" . $this->access_token;
        $content = $this->httpPost($url, $post);
        return json_decode($content);
    }

    /**
     * 事件推送
     * @return void
     */
    public function doMessage($postObj)
    {

    }

    /**
     * 基础消息能力
     * 群发
     **/
    public function sendAll($content)
    {
        $tpl = '{
		   "touser":[
		   %s
		   ],
			"msgtype": "text",
			"text": { "content": "%s"}
		}';
        $curl = 'cgi-bin/message/mass/send?access_token=' . $this->access_token;

        $users = $this->getUserlist();
        for ($i = 0; $i < count($users); $i++) {
            $u .= '"' . $users[$i] . '"';
            if ($i < count($users) - 1)
                $u .= ',';
        }

        $data = sprintf($tpl, $u, $content);
        $result = $this->httpPost($curl, true, "post", $data);
        $result = json_decode($result);
        if ($result->errcode == 0)
            echo "发送成功！";
    }

    /**
     * 用户管理
     * 获取用户列表
     **/
    public function getUserList()
    {
        $url = 'cgi-bin/user/get?access_token=' . $this->access_token;
        $response = $this->client->get($url);
        return $response->getBody();
    }

    /**
     * 账号管理 / 创建二维码ticket
     * @expires_secords：二维码有效期（秒）
     * @scene：场景编号
     **/
    public function getTicket($expires_secords = 604800, $scene = 1)
    {
        if ($expires_secords) {
            //临时二维码的处理
            $data = '{"expire_seconds":' . $expires_secords . ', "action_name": "QR_SCENE", "action_info": {"scene": {"scene_id": ' . $scene . '}}';
        } else {
            //永久二维码的处理
            $data = '{"action_name": "QR_LIMIT_SCENE", "action_info": {"scene": {"scene_id": ' . $scene . '}}';
        }
        return $this->client->post("cgi-bin/qrcode/create?access_token=" . $this->access_token, ['body' => $data]);
    }

    /**
     * 获取二维码
     * @param string ticket
     * @scene：场景编号
     **/
    public function getQRCode($ticket)
    {
        return $this->client->get("https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=" . urlencode($ticket));
    }

    /**
     * 小程序和公众号
     * 统一服务消息
     * $touser
     * $weapp_template_msg
     * $mp_template_msg
     */
    public function uniformSend($keyword, $openid, $template_id, $page, $form_id)
    {
        $url = "cgi-bin/message/wxopen/template/uniform_send?access_token=$this->access_token";
        $weapp_template_msg = [
            'template_id' => $template_id,//小程序模板ID
            'page' => $page,//小程序页面路径
            'form_id' => $form_id,//小程序模板消息formid
            'data' => '',//小程序模板数据
            'emphasis_keyword' => $keyword//小程序模板放大关键词
        ];

        //$mp_template_msg = [
        //    'appid' => '',//公众号appid，要求与小程序有绑定且同主体
        //    'template_id' => '',//公众号模板id
        //    'url'         => '',//公众号模板消息所要跳转的url
        //    'miniprogram' => '',//公众号模板消息所要跳转的小程序，小程序的必须与公众号具有绑定关系
        //    'data'        => ''//公众号模板消息的数据
        //];

        $temp = [
            'token' => $this->config['token'],//接口调用凭证
            'touser' => $openid,//用户openid
            'weapp_template_msg' => $weapp_template_msg,//小程序模板消息相关的信息

        ];
        $response = $this->client->post($url, $temp);
        return $response->getBody();
    }

    /**
     * 智能接口 /AI开放接口
     * 提交语音
     */
    public function addVoice()
    {
        $url = "cgi-bin/media/voice/addvoicetorecofortext?access_token=ACCESS_TOKEN&format=&voice_id=xxxxxx&lang=zh_CN";
    }

    /**
     * 智能接口 /AI开放接口
     * 获取语音识别结果
     * @return void
     */
    public function queryVoice()
    {
        $url = "cgi-bin/media/voice/queryrecoresultfortext?access_token=ACCESS_TOKEN&voice_id=xxxxxx&lang=zh_CN";
    }

    /**
     * 智能接口 /AI开放接口
     * 微信翻译
     * @return array
     */
    public function translate($body, $lfrom = 'zh_CN', $lto = 'en_US')
    {
        $url = "cgi-bin/media/voice/translatecontent?lfrom=$lfrom&lto=$lto&access_token={$this->access_token}";
        $response = $this->client->post($url, ['body' => $body]);
        return $response->getBody();
    }

    /**
     * 小程序
     * 生成Scheme
     * @param string $path
     * @param string $query
     * @return mixed
     */
    public function generateScheme(string $path, string $query)
    {
        $url = "/wxa/generatescheme?access_token={$this->access_token}";
        $bodys = "{\"jump_wxa\":{\"path\":\"" . $path . "\",\"query\":\"" . $query . "\"}}";
        $response = $this->client->post($url, ['body' => $bodys]);
        $data = $response->getBody();
        if ($data["openlink"] != null) {
            return $data["openlink"];
        }
        return $data;
    }

    /**
     * 小程序 / 公众号
     * 身份证OCR识别接口
     * @param string type Front / Back
     * @return void
     */
    public function ocrIdcard($img_url, string $type = 'Front')
    {
        $url = "cv/ocr/idcard?type=$type&img_url=$img_url&access_token={$this->access_token}";
        $response = $this->client->get($url);
        return $response->getBody();
    }

    /**
     * 小程序 / 公众号
     * 银行卡OCR识别接口
     * @return void
     */
    public function ocrBankcard($img_url)
    {
        $url = "cv/ocr/bankcard?img_url=$img_url&access_token={$this->access_token}";
        $response = $this->client->get($url);
        return $response->getBody();
    }

    /**
     * 小程序 / 公众号
     * 营业执照OCR识别接口
     * @return void
     */
    public function ocrBizLicense($img_url)
    {
        $url = "cv/ocr/bizlicense?img_url=$img_url&access_token={$this->access_token}";
        $response = $this->client->get($url);
        return $response->getBody();
    }

    /**
     * 内容安全
     */
    public function mediaCheckAsync()
    {
        $url = "wxa/media_check_async?token=token";
    }

    /**
     * 内容安全
     */
    public function msgSecCheck()
    {
        $url = "wxa/msg_sec_check?token=token";
    }

    /**
     * 获取微信服务器IP地址
     */
    private function getDomainIp()
    {
        $url = "cgi-bin/get_api_domain_ip?access_token=$this->access_token";
        $response = $this->client->get($url);
        return $response->getBody();
    }

    /**
     * 获取微信callback IP地址
     * @return mixed
     */
    public function getCallbackIp()
    {
        $url = "cgi-bin/getcallbackip?access_token=$this->access_token";
        $response = $this->client->get($url);
        return $response->getBody();
    }

    /**
     * 网络检测
     * @return void
     */
    public function check()
    {
        $url = "cgi-bin/callback/check?access_token=$this->access_token";
        $response = $this->client->get($url);
        return $response->getBody();
    }

    /**
     * 验证函数
     * @return void
     */
    public function valid()
    {
        if (!empty($_GET["echostr"])) {
            if ($this->checkSignature()) {
                echo 'success';
                exit;
            }
        }
    }

    /**
     * 验证消息的确来自微信服务器
     * @return bool
     */
    private function checkSignature(): bool
    {
        $signature = $_GET["signature"];
        $timestamp = $_GET["timestamp"];
        $nonce = $_GET["nonce"];

        $token = $this->config['token'];
        $tmpArr = array($token, $timestamp, $nonce);
        sort($tmpArr, SORT_STRING);
        $tmpStr = implode($tmpArr);
        $tmpStr = sha1($tmpStr);

        if ($tmpStr == $signature) {
            return true;
        } else {
            return false;
        }
    }

    public function getSignPackage()
    {
        $jsapiTicket = $this->getJsApiTicket();

        // 注意 URL 一定要动态获取，不能 hardcode.
        $protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://";
        $url = "$protocol$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]";

        $timestamp = time();
        $nonceStr = $this->createNonceStr();

        // 这里参数的顺序要按照 key 值 ASCII 码升序排序
        $string = "jsapi_ticket=$jsapiTicket&noncestr=$nonceStr&timestamp=$timestamp&url=$url";

        $signature = sha1($string);

        return array(
            "appId" => $this->config['appId'],
            "nonceStr" => $nonceStr,
            "timestamp" => $timestamp,
            "url" => $url,
            "signature" => $signature,
            "rawString" => $string
        );
    }

    private function createNonceStr($length = 16)
    {
        $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        $str = "";
        for ($i = 0; $i < $length; $i++) {
            $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
        }
        return $str;
    }

    private function getJsApiTicket()
    {
        $file = 'jsapi_ticket.json';
        if (!file_exists($file)) {
            file_put_contents($file, '');
        }
        $data = json_decode(file_get_contents($file));
        if (empty($data) || $data->expire_time < time()) {
            $accessToken = $this->getAccessToken();
            $url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi&access_token=$accessToken";
            $response = $this->client->get($url);
            $data = json_decode($response->getContents());
            $ticket = $data->ticket;
            if ($ticket) {
                $data->expire_time = time() + 4000;
                $data->jsapi_ticket = $ticket;
                file_put_contents($file, json_encode($data));
            }
        } else {
            $ticket = $data->jsapi_ticket;
        }
        return $ticket;
    }

    /**
     * 对微信小程序用户加密数据的解密 检验数据的真实性，并且获取解密后的明文
     * @param $sessionKey string 会话密钥
     * @param $encryptedData string 加密的用户数据
     * @param $iv string 与用户数据一同返回的初始向量
     * @param $data string 解密后的原文
     *
     * @return int 成功0，失败返回对应的错误码
     */
    public function decryptData($sessionKey, $encryptedData, $iv, &$data)
    {
        if (strlen($sessionKey) != 24) {
            return -41001;
        }
        $aesKey = base64_decode($sessionKey);

        if (strlen($iv) != 24) {
            return -41002;
        }
        $aesIV = base64_decode($iv);

        $aesCipher = base64_decode($encryptedData);

        $result = openssl_decrypt($aesCipher, "AES-128-CBC", $aesKey, 1, $aesIV);

        $dataObj = json_decode($result);
        if ($dataObj == NULL) {
            return -41003;
        }
        if ($dataObj->watermark->appid != $this->config['appId']) {
            return -41003;
        }
        $data = $result;
        return 0;
    }
}
